package edu.tsinghua.oxy.engineer.service.impl;

import edu.tsinghua.oxy.common.constant.SpiderConstants;
import edu.tsinghua.oxy.common.repository.IBaseRedisRepository;
import edu.tsinghua.oxy.common.utils.UrlUtils;
import edu.tsinghua.oxy.engineer.service.IUrlService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.net.MalformedURLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Url调度器的具体实现
 *
 * @author sean
 * @date 2021 - 07 - 29 下午3:51
 */
@Slf4j
@Service
public class UrlServiceImpl implements IUrlService {

    @Resource
    IBaseRedisRepository redisRepository;

    @Resource
    UrlUtils urlUtils;


    /**
     * 从队列中获取url，目前的策略是：
     *      1.先从高优先级url队列中获取
     *      2.再从低优先级url队列中获取
     *  对应我们的实际场景，应该是先解析完列表url再解析详情url
     *  但是需要注意的是，在分布式多线程的环境下，是不能完全保证的，因为在某个时刻高优先级url队列中
     *  的url消耗完了，但可能下一个高优先级url还未入队，此时，其它线程或进程无法获取到高优先级队列url
     *  这时就会去获取低优先级队列中的url，而且由于网络的不稳定性，代理也有被封禁的可能，所以URL可能会下载失败
     *  此时尝试重新消费失败的URL
     * @return
     */
    @Override
    public Map<String,String> poll() {
        Map<String,String> resultMap = new HashMap<>(4);
        // 从set中随机获取一个顶级域名
        String randomDomain = redisRepository.srandmember(SpiderConstants.SPIDER_WEBSITE_DOMAINS_KEY);
        String key = randomDomain + SpiderConstants.SPIDER_DOMAIN_HIGHER_SUFFIX;
        String url = redisRepository.spop(key);
        resultMap.put(SpiderConstants.SPIDER_DOMAIN_HIGHER_SUFFIX,url);
        // 如果为null，则从低优先级中获取
        if(url == null) {
            key = randomDomain + SpiderConstants.SPIDER_DOMAIN_LOWER_SUFFIX;
            url = redisRepository.spop(key);
            resultMap.put(SpiderConstants.SPIDER_DOMAIN_LOWER_SUFFIX,url);
        }
        // 如果依然为null，则从处理失败的高优先级队列获取
        if(url == null) {
            key = randomDomain + SpiderConstants.SPIDER_DOMAIN_HIGHER_ERROR_SUFFIX;
            url = redisRepository.spop(key);
            resultMap.put(SpiderConstants.SPIDER_DOMAIN_HIGHER_SUFFIX,url);
        }
        // 如果依然为null，则从处理失败的低优先级队列获取
        if(url == null) {
            key = randomDomain + SpiderConstants.SPIDER_DOMAIN_LOWER_ERROR_SUFFIX;
            url = redisRepository.spop(key);
            resultMap.put(SpiderConstants.SPIDER_DOMAIN_LOWER_SUFFIX,url);
        }
        return resultMap;
    }

    /**
     * 向高优先级列表中添加列表url
     * @param highUrl
     * @throws MalformedURLException
     */
    @Override
    public void offerHigher(String highUrl) throws MalformedURLException {
        offerUrl(highUrl, SpiderConstants.SPIDER_DOMAIN_HIGHER_SUFFIX);
    }

    /**
     * 向高优先级列表中添加列表url
     * @param domain
     * @param highUrl
     */
    @Override
    public void offerHigher(String domain, String highUrl) {
        offerUrl(domain, highUrl, SpiderConstants.SPIDER_DOMAIN_HIGHER_SUFFIX);
    }

    /**
     * 向高优先级列表中添加处理失败的列表url
     * @param domain
     * @param highUrl
     */
    @Override
    public void offerErrorHigher(String domain, String highUrl) {
        offerUrl(domain,highUrl,SpiderConstants.SPIDER_DOMAIN_HIGHER_ERROR_SUFFIX);
    }

    /**
     * 向低优先级列表中添加详情url
     * @param lowUrl
     * @throws MalformedURLException
     */
    @Override
    public void offerLower(String lowUrl) throws MalformedURLException {
        offerUrl(lowUrl, SpiderConstants.SPIDER_DOMAIN_LOWER_SUFFIX);
    }

    /**
     * 向低优先级列表中添加处理失败的详情url
     * @param domain
     * @param lowUrl
     */
    @Override
    public void offerErrorLower(String domain, String lowUrl) {
        offerUrl(domain,lowUrl,SpiderConstants.SPIDER_DOMAIN_LOWER_ERROR_SUFFIX);
    }

    /**
     * 批量写入低优先级队列详情url
     * @param domain
     * @param lowUrls
     */
    @Override
    public void offerLower(String domain, List<String> lowUrls) {
        offerUrl(domain,lowUrls,SpiderConstants.SPIDER_DOMAIN_LOWER_SUFFIX);
    }

    /**
     * 添加url的通用方法
     * @param url   需要添加的url
     * @param urlTypeSuffix  url类型后缀.higher或.lower
     */
    public void offerUrl(String url, String urlTypeSuffix) throws MalformedURLException {
        // 获取url对应的顶级域名，如jd.com
        String domain = urlUtils.getDomainName(url);

        offerUrl(domain,url,urlTypeSuffix);
    }

    /**
     * 添加url的通用方法
     * @param domain   域名
     * @param url   需要添加的url
     * @param urlTypeSuffix  url类型后缀.higher或.lower
     */
    public void offerUrl(String domain, String url, String urlTypeSuffix) {
        // 拼接url队列的key，如jd.com.higher
        String key = domain + urlTypeSuffix;
        // 向url队列中添加url
        redisRepository.sadd(key, url);
    }

    /**
     * 添加url的通用方法
     * @param domain   域名
     * @param urls   需要添加的url列表
     * @param urlTypeSuffix  url类型后缀.higher或.lower
     */
    public void offerUrl(String domain, List<String> urls, String urlTypeSuffix) {
        // 拼接url队列的key，如jd.com.higher
        String key = domain + urlTypeSuffix;
        // 向url队列中添加url
        redisRepository.sadd(key, urls);
    }

    /**
     * 获取下一页的页码
     * @param domain
     * @return Long
     */
    @Override
    public Long getNextPage(String domain) {
        return redisRepository.incr(domain + SpiderConstants.SPIDER_DOMAIN_PAGE_SUFFIX);
    }
}
